﻿using System;
using System.Collections.Generic;
using System.Reflection;
using ELW.Library.Math.Calculators;

namespace ELW.Library.Math
{
    /// <summary>
    /// Supported operations and configuration manager.
    /// </summary>
    public sealed class OperationsRegistry
    {
        private readonly List<Operation> operationsList = new List<Operation>();
        private readonly Dictionary<int, PriorityAssociation> priorityAssociationsDictionary = new Dictionary<int, PriorityAssociation>();
        private readonly Dictionary<string, Operation> operationNamesDictionary = new Dictionary<string, Operation>();
        private readonly Dictionary<string, ICollection<Operation>> operationSignaturesDictionary = new Dictionary<string, ICollection<Operation>>();

        public bool IsPriorityDefined(int priority)
        {
            return priorityAssociationsDictionary.ContainsKey(priority);
        }

        public PriorityAssociation GetAssociationByPriority(int priority)
        {
            if (!IsPriorityDefined(priority))
                throw new ArgumentException("Specified priority is not defined.", "priority");
            return priorityAssociationsDictionary[priority];
        }

        public bool IsOperationDefined(string name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            if (name.Length == 0)
                throw new ArgumentException("String is empty.", "name");
            //
            return operationNamesDictionary.ContainsKey(name);
        }

        public Operation GetOperationByName(string name)
        {
            if (!IsOperationDefined(name))
                throw new ArgumentException("No operation defined with name specified.", "name");
            return operationNamesDictionary[name];
        }

        public bool IsSignatureDefined(string signature)
        {
            if (signature == null)
                throw new ArgumentNullException("signature");
            if (signature.Length == 0)
                throw new ArgumentException("String is empty.", "signature");
            //
            return operationSignaturesDictionary.ContainsKey(signature);
        }

        public ICollection<Operation> GetOperationsUsingSignature(string signature)
        {
            if (!IsSignatureDefined(signature))
                throw new ArgumentException("No operation uses signature specified.", "signature");
            //
            return operationSignaturesDictionary[signature];
        }

        private readonly int[] signaturesLens;
        public int[] SignaturesLens
        {
            get
            {
                return signaturesLens;
            }
        }

        /// <summary>
        /// Инициализация операторов
        /// </summary>
        private void Initialize()
        {

            #region Добавление приоритетов и ассоциаций
            priorityAssociationsDictionary.Add(1, PriorityAssociation.LeftAssociated);
            priorityAssociationsDictionary.Add(2, PriorityAssociation.RightAssociated);
            priorityAssociationsDictionary.Add(3, PriorityAssociation.LeftAssociated);
            priorityAssociationsDictionary.Add(4, PriorityAssociation.LeftAssociated);
            priorityAssociationsDictionary.Add(5, PriorityAssociation.LeftAssociated);
            priorityAssociationsDictionary.Add(6, PriorityAssociation.LeftAssociated);
            priorityAssociationsDictionary.Add(7, PriorityAssociation.LeftAssociated);
            #endregion

            #region Зарегестрированные операции

            #region Математические операторы

            // возведение в степень
            operationsList.Add(new Operation("powering", OperationKind.Operator,
                        new string[] { "^" }, 2,
                        (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorPowering", false), 1));

            // минус
            operationsList.Add(new Operation("negation", OperationKind.Operator,
                        new string[] { "-" }, 1,
                        (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorNegation", false), 2));

            //плюс
            operationsList.Add(new Operation("positivation", OperationKind.Operator,
                        new string[] { "+" }, 1,
                        (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorPositivation", false), 2));

            //умножение
            operationsList.Add(new Operation("multiplication", OperationKind.Operator,
                        new string[] { "*" }, 2,
                        (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorMultiplication", false), 3));

            //деление
            operationsList.Add(new Operation("division", OperationKind.Operator,
                        new string[] { "/" }, 2,
                        (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorDivision", false), 3));

            //сумма
            operationsList.Add(new Operation("addition", OperationKind.Operator,
                        new string[] { "+" }, 2,
                        (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorAddition", false), 4));

            //вычитание
            operationsList.Add(new Operation("subtraction", OperationKind.Operator,
                        new string[] { "-" }, 2,
                        (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorSubtraction", false), 4));

            #endregion

            #region Логические операторы

            // не
            operationsList.Add(new Operation("not", OperationKind.Operator,
                    new string[] { "!" }, 1,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorNOT", false), 2));

            // больше
            operationsList.Add(new Operation("greater", OperationKind.Operator,
                    new string[] { ">" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorGREATER", false), 5));

            // больше или равно
            operationsList.Add(new Operation("greater_or_equal", OperationKind.Operator,
                    new string[] { ">=" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorGREATERorNOT", false), 5));

            // меньше
            operationsList.Add(new Operation("less", OperationKind.Operator,
                new string[] { "<" }, 2,
                (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorLESS", false), 5));

            // меньше или равно
            operationsList.Add(new Operation("less_or_equal", OperationKind.Operator,
                    new string[] { "<=" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorLESSorNOT", false), 5));

            // равно
            operationsList.Add(new Operation("equal", OperationKind.Operator,
                    new string[] { "=" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorEQUAL", false), 5));

            // не равно
            operationsList.Add(new Operation("unequal", OperationKind.Operator,
                    new string[] { "!=" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorUNEQUAL", false), 5));

            // и
            operationsList.Add(new Operation("and", OperationKind.Operator,
                    new string[] { "AND" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorAND", false), 6));

            // или
            operationsList.Add(new Operation("or", OperationKind.Operator,
                    new string[] { "OR" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorOR", false), 7));

            #endregion

            #region Функции

            // синус
            operationsList.Add(new Operation("sinus", OperationKind.Function,
                    new string[] { "sin" }, 1,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorSinus", false)));

            // косинус
            operationsList.Add(new Operation("cosinus", OperationKind.Function,
                    new string[] { "cos" }, 1,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.CalculatorCosinus", false)));

            // нормальное распределение
            operationsList.Add(new Operation("rndN", OperationKind.Function,
                    new string[] { "rndN" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.Calculator_rndN", false)));

            // экспонициальное распределение
            operationsList.Add(new Operation("rndE", OperationKind.Function,
                    new string[] { "rndE" }, 1,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.Calculator_rndE", false)));

            // равномерное распределение
            operationsList.Add(new Operation("rndU", OperationKind.Function,
                    new string[] { "rndU" }, 2,
                    (IOperationCalculator)Assembly.Load("ELW.Library.Math").CreateInstance("ELW.Library.Math.Calculators.Standard.Calculator_rndU", false)));

            #endregion
            #endregion
        }

        public OperationsRegistry()
        {
            Initialize();
            // Storing signatures lenghts has been met during processing
            List<int> lens = new List<int>();
            foreach (Operation operation in operationsList)
            {
                operationNamesDictionary.Add(operation.Name, operation);
                //
                foreach (string s in operation.Signature)
                {
                    if (!operationSignaturesDictionary.ContainsKey(s))
                        operationSignaturesDictionary.Add(s, new List<Operation>());
                    operationSignaturesDictionary[s].Add(operation);
                }
                // Add signature lenght if not added already
                foreach (string s in operation.Signature)
                {
                    int len = s.Length;
                    bool alreadySaved = false;
                    foreach (int i in lens)
                    {
                        if (i == len)
                        {
                            alreadySaved = true;
                            break;
                        }
                    }
                    if (!alreadySaved)
                        lens.Add(len);
                }
            }
            lens.Sort();
            signaturesLens = new int[lens.Count];
            lens.CopyTo(signaturesLens);
        }
    }
}
